import asyncio
import copy
import itertools
import re
import time
from datetime import datetime, timedelta
from typing import List

from bson import ObjectId

import schemas
from controller.activity import search_activity_by_param
from controller.alumni import parse_alumni_search_results
from exceptions import FieldCheckError, AlterModelError, ModelReferenceError
from ioservice.activity import check_activities_exist_by_ids, find_activities_by_query
from ioservice.activity_institution import find_institution_records_by_combined_ids, bulk_replace_institution_records, \
    bulk_insert_institutions, count_institution_records, find_institution_records_by_query
from ioservice.alumni import check_alumni_exists_by_ids, search_alumni
from ioservice.common.basic import mdb
from ioservice.common.mongo import count_documents, delete_models, aggregate_docs
from ioservice.const import alumni_col, activity_col, activity_institution_col, institution_col, top11_col, \
    institution_top11_col
from ioservice.institution import sync_institutions_data, check_institution_exists_by_ids
from models.activity import ActivityInstitution, ValidateModel, RoleAlumni
from models.alumni import Alumni, AlumniSearchModel
from schemas import AlterModelResult
from task_utils.task_es import job_update_activity_contacts
from task_utils.task_jobs import job_join_act_contacts
from tasks.computing import alumni as alumni_computing


async def bulk_check_institution_records(records: List[ActivityInstitution]):
    institution_ids, activity_ids = set(), set()
    for r in records:
        if r.institution_id:
            institution_ids.add(str(r.institution_id))
        if r.activity_id:
            activity_ids.add(str(r.activity_id))
    tasks = {
        "institution_check": asyncio.create_task(check_institution_exists_by_ids(list(institution_ids))),
        "activity_check": asyncio.create_task(check_activities_exist_by_ids(list(activity_ids))),
    }
    errors = []
    missing_people = await tasks['institution_check']
    if missing_people:
        errors.append(
            FieldCheckError(field="institution_id", error=f"There are non existent ids: {', '.join(missing_people)}"))
    missing_activity = await tasks['activity_check']
    if missing_activity:
        errors.append(
            FieldCheckError(field="activity_id", error=f"There are non existent ids: {', '.join(missing_activity)}"))
    if errors:
        raise ModelReferenceError("activity_institution", details=errors)


async def separate_records(records: List[ActivityInstitution]):
    record_ids = {r.combined_id for r in records}
    old_records = await find_institution_records_by_combined_ids(list(record_ids),
                                                              fields=["institution_id", "activity_id", "is_attended"])
    logger.info(f"separate_records::old_records: {old_records}")
    old_id2record = {r.combined_id: r for r in old_records}
    inserted, updated, errors = list(), list(), list()
    for i, r in enumerate(records):
        if r.combined_id in old_id2record:
            old = old_id2record[r.combined_id]
            # 已经参会人员不允许改为未参会
            if r.is_attended is False and old.is_attended is True:
                errors.append(
                    FieldCheckError(num=i, field="is_attended", error="该校友已经参加过该活动，不能更新为未参加"))
                continue
            updated.append(r)
        else:
            inserted.append(r)
    return inserted, updated, errors


async def bulk_alter_institutions_with_checking(records: List[ActivityInstitution]):
    """
    批量编辑
    """
    await bulk_check_institution_records(records)
    inserted, updated, errors = await separate_records(records)
    if errors:
        raise AlterModelError("activity_institution", errors)
    tasks = {}
    if inserted:
        tasks['inserted'] = asyncio.create_task(bulk_insert_institutions(inserted))
    if updated:
        tasks['updated'] = asyncio.create_task(bulk_replace_institution_records(updated))

    result = AlterModelResult()
    if 'inserted' in tasks:
        insert_ids = await tasks['inserted']
        if insert_ids:
            result.ids = [str(i) for i in insert_ids]
            result.created_num = len(insert_ids)
    if 'updated' in tasks:
        modify_num = await tasks['updated']
        result.modified_num = modify_num

    asyncio.ensure_future(after_alter_institution_record(inserted, updated))
    return result


async def after_alter_institution_record(inserted, updated):
    """
    参考需求说明：
    - https://fs1f1atlth.feishu.cn/docs/doccnMYmyK4MLKb5uUYKWBLZsAh
    """
    institution_ids = {str(r.institution_id) for r in itertools.chain(inserted, updated) if r.is_attended}
    if institution_ids:
        logger.info(f"after_alter_institution_record::update_alumni_computing_fields people number: {len(institution_ids)}")
        # await alumni_computing.batch_update_computing_fields(list(institution_ids),
        #                                                      caller="controller::after_alter_institution_record")
    activity_ids = {r.activity_id for r in inserted if r.is_attended}
    if not activity_ids:
        return
    logger.info(
        f"after_alter_institution_record::update_institution_computing_fields activities number: {len(activity_ids)}")
    query = {"_id": {"$in": list(activity_ids)}}
    activities = await find_activities_by_query(query, 0, 0, fields=["institution_id"])
    institution_ids = {str(a.institution_id) for a in activities}
    await sync_institutions_data(list(institution_ids))


async def get_institution_records(params: schemas.SearchParam):
    """
    dataapi 使用
    """
    query = params.get_mongo_query(schemas.InstitutionRecordsFilter)
    if params.current:
        params.initial_pagination()
    if not params.sort:
        params.sort = ["-create_time"]
    copy_query = copy.copy(query)
    total = await count_institution_records(copy_query)

    items = await agg_stage_institution_alumni(query, params)
    #  更新活动报名机构数
    if copy_query.get("activity_id"):
        update_act_ids = [str(copy_query.get("activity_id"))]
        job_join_act_contacts.apply_async(args=[update_act_ids], queue="high")
    job_update_activity_contacts.apply_async(args=[update_act_ids[0]], queue='es')
    return schemas.SearchResult(items=items, total=total)


async def agg_stage_institution_alumni(match_stage, params):
    pipe = []
    name = None
    offset = params.offset or 0
    limit = params.size or 10

    in_kexie = None

    institution_type = None

    if match_stage:
        if "institution_name" in match_stage:
            name = match_stage['institution_name']
            del match_stage['institution_name']
        if "in_kexie" in match_stage:
            in_kexie = match_stage['in_kexie']
            del match_stage['in_kexie']

        if "institution_type" in match_stage:
            institution_type = match_stage['institution_type']
            del match_stage['institution_type']

    pipe.append({"$match": match_stage})

    stage_join = [

        {"$lookup": {
            "from": "institution",
            "let": {"person_ref": "$institution_id"},
            "pipeline": [
                {"$match": {"$expr":
                                {"$and": [{"$eq": ["$_id", "$$person_ref"]},
                                          ]}
                            }},
                {"$project": {"id": "$_id", "institution_name": 1, "in_kexie": 1, "institution_type": 1, "institution_location": 1,
                              "cur_institution_department": 1, "cur_institution_name": 1, "identify_id": 1,
                              "identify_photo": 1, "identify_type": 1,
                              "phone": 1, "_id": 0}}
            ],
            "as": 'participants'
        }},

    ]
    pipe.extend(stage_join)
    if name:
        _name = re.compile(re.escape(name.strip()))
        pipe.append({"$match": {"participants.institution_name": {"$regex": name}}})

    if in_kexie is not None:
        print("!!!!!",in_kexie)
        if in_kexie:
            pipe.append({"$match": {"participants.in_kexie": True}})
        else:
            pipe.append({"$match": {"participants.in_kexie": False}})
    if institution_type:
        pipe.append({"$match": {"participants.institution_type": institution_type}})

    stage_root = [
        {"$addFields": {'institution': {"$arrayElemAt": ["$participants", 0]}}},
        {"$unset": "participants"},
        {"$skip": offset}, {"$limit": limit},
    ]

    pipe.extend(stage_root)
    agg_results = await aggregate_docs(activity_institution_col, pipe)
    return agg_results


async def get_institution_records_by_person(params: schemas.SearchParam):
    query = {"person_id": ObjectId(params.filter.pop('person_id')), "is_attended": params.filter.pop('is_attended')}
    records = await find_institution_records_by_query(query, None, 0, 0, None)
    aid2record = {r.activity_id: r for r in records}
    if not aid2record:
        return schemas.SearchResult()
    activity_filters = schemas.search.ActivityFilter(
        ids=[str(i) for i in aid2record.keys()],
        **params.filter
    )
    params.filter = activity_filters.dict(exclude_unset=True)
    results = await search_activity_by_param(params)
    items = list()
    for activity in results.items:
        record = aid2record[activity.id]
        record.person = activity
        items.append(record)
    results.items = items
    return results


async def get_institution_records_by_activity(params: schemas.SearchParam):
    query = {"activity_id": ObjectId(params.filter.pop("activity_id")), "is_attended": params.filter.pop('is_attended')}
    records = await find_institution_records_by_query(query, None, 0, 0, None)
    pid2record = {str(r.person_id): r for r in records}
    if not pid2record:
        return schemas.SearchResult()
    params.filter["ids"] = [i for i in pid2record.keys()]
    person_search_cxt = AlumniSearchModel(query=params.filter, is_deleted=params.filter.pop('is_deleted', False),
                                          offset=params.offset, size=params.size, sort=params.sort)
    total, people = await search_alumni(person_search_cxt)
    await parse_alumni_search_results(people, person_search_cxt)
    items = list()
    for person in people:
        record = pid2record[person['id']]
        record.person = Alumni.parse_obj(person)
        items.append(record)
    return schemas.SearchResult(items=items, total=total)


async def get_institution_records_separately(params: schemas.SearchParam):
    """
    分别搜索：
    - 参加活动的校友列表：当 filter 中带有 activity_id
    - 校友参加的活动列表:当 filter 中带有 person_id
    :return:
    """
    if params.current:
        params.initial_pagination()
    if not params.filter:
        return schemas.SearchResult()
    if 'activity_id' in params.filter:  # 活动参与人列表
        return await get_institution_records_by_activity(params)
    if 'person_id' in params.filter:  # 校友参加的活动列表
        return await get_institution_records_by_person(params)
    raise ValueError("filter requires activity_id or person_id")


async def crud_validate_label(item: ValidateModel) -> int:
    label = item.label.strip().lower()
    query = {"name": label, "institution_id": ObjectId(item.institution_id), "is_deleted": {"$ne": True}}
    counts = await count_documents(activity_col, query)
    return counts


async def crud_delete_related_institution(activity_id: str, institution_id: str):
    query = {"activity_id": ObjectId(activity_id), "institution_id": ObjectId(institution_id)}
    remove = await delete_models(activity_institution_col, query)
    return remove


from loguru import logger


async def crud_role_related_alumni(item: RoleAlumni):
    col = mdb.get_collection(activity_institution_col)
    item.is_attended = True
    await col.update_one({"activity_id": ObjectId(item.activity_id), "person_id": ObjectId(item.person_id)},
                         {"$set": item.dict(exclude_none=True, exclude={"activity_id", "person_id"})}, upsert=True)

    col = mdb.get_collection(alumni_col)

    person = await col.find_one({"_id": ObjectId(item.person_id)})

    job_update_activity_contacts.apply_async(args=[item.activity_id, item.person_id], queue='es')
    print('333', item.activity_id, item.person_id)

    if person:
        # 如果发现更新

        role = person.get("role_by", None)
        if not role:
            # 创建array
            await col.update_one({"_id": ObjectId(item.person_id)},
                                 {"$push": {"role_by": item.dict(
                                     include={"activity_id", "role", "level_of_role", "per_act_time"})}})

        # 存在的情况
        if isinstance(role, List):
            ins = await col.find_one_and_update(
                {"_id": ObjectId(item.person_id), "role_by.activity_id": item.activity_id},
                {"$set": {"role_by.$.role": item.role,
                          "role_by.$.level_of_role": item.level_of_role,
                          "role_by.$.per_act_time": item.per_act_time}})

            if ins is None:
                await col.update_one({"_id": ObjectId(item.person_id)},
                                     {"$addToSet": {
                                         "role_by": item.dict(
                                             include={"activity_id", "role", "level_of_role", "per_act_time"})}})
        else:
            await col.update_one({"_id": ObjectId(item.person_id)},
                                 {"$set": {"role_by": [
                                     item.dict(include={"activity_id", "role", "level_of_role", "per_act_time"})]}})
        return True


async def crud_metric_by_activity_type(institution_id, start_year, end_year, only_year):
    """活动情况统计
    @only_year flag 刷新
    """
    end_year = end_year or start_year

    from_date = datetime(start_year, 1, 1) - timedelta(hours=8)
    to_date = datetime(end_year + 1, 1, 1) - timedelta(hours=8)
    year_range = {"$gte": from_date,
                  "$lt": to_date}
    pipeline = []
    # filter match
    stage_filter = [
        {"$match": {"end_time": year_range, "is_deleted": {"$ne": True}}},
    ]
    if institution_id:
        stage_filter.append({"$match": {"$or": [
            {"institution_id": ObjectId(institution_id)},
            {"co_inst_ids": ObjectId(institution_id)}]
        }})
    logger.info(stage_filter)
    stage_lookup = [
        {"$project": {"start_time:": 1, "end_time": 1, "co_inst_ids": 1, "institution_id": 1, "activity_type": 1}},
        {"$lookup": {
            "from": "activity_institution",
            "let": {"activity_ref_id": "$_id"},
            "pipeline": [
                {"$match": {"is_deleted": {"$ne": True}}},
                {"$match": {"$expr": {"$eq": ["$activity_id", "$$activity_ref_id"]}}},
                {"$project": {"_id": 1}}
            ],
            "as": 'participants'
        }},
        {"$addFields": {"numOfParticipants": {"$size": "$participants"}}},
    ]
    facet_stage = {"$facet": {
        # 根据年获得活动数据
        "byMonth": [
            {"$group": {
                "_id": {
                    "perMonth": {"$month": "$end_time"},
                    "activity_type": "$activity_type"
                },
                "total_type_month": {"$sum": 1}
            }},
            {"$sort": {"_id.perMonth": 1}},
            {"$replaceWith": {
                "$mergeObjects": ["$$ROOT", "$_id"]
            }},
            {"$match": {"perMonth": {"$ne": None}, "activity_type": {"$ne": None}}},
            {"$project": {"_id": 0, "total_attr": "$total_type_month", "metadata": 1, "attr": "$perMonth",
                          "activity_type": 1}}
        ],
        "byQuarter": [
            {"$group": {
                "_id": {
                    "quarter": {"$dateTrunc": {"date": "$end_time", "unit": "quarter", "timezone": "Asia/Shanghai",
                                               "binSize": 1}},
                    "activity_type": "$activity_type"
                },
                "total_type_quarter": {"$sum": 1},
                "metadata": {"$addToSet": {"$month": "$end_time"}},
            }},
            {"$replaceWith": {
                "$mergeObjects": ["$$ROOT", "$_id"]
            }},
            {"$match": {"quarter": {"$ne": None}, "activity_type": {"$ne": None}}},
            {"$sort": {"quarter": 1}},
            {"$project": {"total_attr": "$total_type_quarter", "activity_type": 1, "quarter": 1, "_id": 0,
                          "attr": {
                              "$switch": {
                                  "branches": [
                                      {"case": {"$eq": [{"$month": "$quarter"}, 9]},
                                       "then": "Q4"},
                                      {"case": {"$eq": [{"$month": "$quarter"}, 6]},
                                       "then": "Q3"},
                                      {"case": {"$eq": [{"$month": "$quarter"}, 3]},
                                       "then": "Q2"},
                                      {"case": {"$eq": [{"$month": "$quarter"}, 12]},
                                       "then": "Q1"}
                                  ],
                              }
                          }
                          },

             }
        ],
    }}
    if only_year:
        facet_stage = {"$facet": {"byYear": [
            {"$group": {
                "_id": {
                    "year": {"$dateTrunc": {"date": "$end_time", "unit": "year", "timezone": "Asia/Shanghai",
                                            "binSize": 1}},
                    "activity_type": "$activity_type"
                },
                "total_type_year": {"$sum": 1},
                "metadata": {"$addToSet": {"$year": "$end_time"}},
            }},
            {"$replaceWith": {
                "$mergeObjects": ["$$ROOT", "$_id"]
            }},
            {"$match": {"year": {"$ne": None}, "activity_type": {"$ne": None}}},
            {"$sort": {"year": 1}},
            {"$project": {"_id": 0, "total_attr": "$total_type_year", "metadata": 1, "activity_type": 1, "year": 1,
                          "attr": {"$first": "$metadata"}}}
        ]}}

    pipeline.extend(stage_filter)
    stage_lookup.append(facet_stage)
    pipeline.extend(stage_lookup)

    result = await aggregate_docs(activity_col, pipeline)
    return result


async def crud_metric_by_activity_head(institution_id, start_year, end_year, only_year):
    end_year = end_year or start_year

    from_date = datetime(start_year, 1, 1) - timedelta(hours=8)
    to_date = datetime(end_year + 1, 1, 1) - timedelta(hours=8)
    year_range = {"$gte": from_date,
                  "$lt": to_date}
    pipeline = []
    stage_filter = [
        {"$match": {"end_time": year_range, "is_deleted": {"$ne": True}}},
    ]
    if institution_id:
        stage_filter.append({"$match": {"$or":
                                            [{"institution_id": ObjectId(institution_id)},
                                             {"co_inst_ids": ObjectId(institution_id)}]
                                        }})

    stage_lookup = [
        {"$project": {"start_time:": 1, "end_time": 1, "co_inst_ids": 1, "institution_id": 1, "activity_type": 1}},
        {"$lookup": {
            "from": "activity_institution",
            "let": {"activity_ref_id": "$_id"},
            "pipeline": [
                {"$match": {"is_deleted": {"$ne": True}}},
                {"$match": {"$expr": {"$eq": ["$activity_id", "$$activity_ref_id"]}}},
                {"$project": {"_id": 1}}
            ],
            "as": 'participants'
        }},
        {"$addFields": {"numOfParticipants": {"$size": "$participants"}}},
    ]
    facet_stage = {"$facet": {
        # 月度
        "byMonth": [
            {"$group": {
                "_id": {
                    "perMonth": {"$month": "$end_time"},
                    "activity_type": "$activity_type"
                },
                "total_participants": {"$sum": "$numOfParticipants"}
            }},
            {"$sort": {"_id.perMonth": 1}},
            {"$replaceWith": {
                "$mergeObjects": ["$$ROOT", "$_id"]
            }},
            {"$match": {"perMonth": {"$ne": None}}},
            {"$project": {"_id": 0, "total_attr": "$total_participants", "metadata": 1, "attr": "$perMonth",
                          "activity_type": 1}}
        ],
        # 季度
        "byQuarter": [
            {"$group": {
                "_id": {
                    "quarter": {"$dateTrunc": {"date": "$end_time", "unit": "quarter", "timezone": "Asia/Shanghai",
                                               "binSize": 1}},
                    "activity_type": "$activity_type"
                },
                "total_type_quarter": {"$sum": "$numOfParticipants"},
                "metadata": {"$addToSet": {"$month": "$end_time"}},
            }},
            {"$replaceWith": {
                "$mergeObjects": ["$$ROOT", "$_id"]
            }},
            {"$match": {"quarter": {"$ne": None}, "activity_type": {"$ne": None}}},
            {"$sort": {"quarter": 1}},
            {"$project": {"total_attr": "$total_type_quarter", "activity_type": 1, "quarter": 1, "_id": 0,
                          "attr": {
                              "$switch": {
                                  "branches": [
                                      {"case": {"$eq": [{"$month": "$quarter"}, 9]},
                                       "then": "Q4"},
                                      {"case": {"$eq": [{"$month": "$quarter"}, 6]},
                                       "then": "Q3"},
                                      {"case": {"$eq": [{"$month": "$quarter"}, 3]},
                                       "then": "Q2"},
                                      {"case": {"$eq": [{"$month": "$quarter"}, 12]},
                                       "then": "Q1"}
                                  ],
                              }
                          }
                          },
             }
        ]
        # 年度
    }}
    if only_year:
        facet_stage = {"$facet": {"byYear": [
            {"$group": {
                "_id": {
                    "year": {"$dateTrunc": {"date": "$end_time", "unit": "year", "timezone": "Asia/Shanghai",
                                            "binSize": 1}},
                    "activity_type": "$activity_type"
                },
                "total_type_year": {"$sum": "$numOfParticipants"},
                "metadata": {"$addToSet": {"$year": "$end_time"}},
            }},
            {"$replaceWith": {
                "$mergeObjects": ["$$ROOT", "$_id"]
            }},
            {"$match": {"year": {"$ne": None}, "activity_type": {"$ne": None}}},
            {"$sort": {"year": 1}},
            {"$project": {"_id": 0, "total_attr": "$total_type_year", "metadata": 1, "activity_type": 1, "year": 1,
                          "attr": {"$first": "$metadata"}}}
        ]}}

    pipeline.extend(stage_filter)
    stage_lookup.append(facet_stage)
    pipeline.extend(stage_lookup)
    result = await aggregate_docs(activity_col, pipeline)
    return result


async def crud_metric_by_activity_ranking(institution_id, start_year, end_year):
    """获得年度排行"""
    end_year = end_year or start_year

    from_date = datetime(start_year, 1, 1) - timedelta(hours=8)
    to_date = datetime(end_year + 1, 1, 1) - timedelta(hours=8)
    year_range = {"$gte": from_date,
                  "$lt": to_date}
    pipeline = []
    stage_filter = [
        {"$match": {"is_deleted": {"$ne": True}, "level": 1}},
    ]
    stage_lookup = [
        # stage2 institution label
        {"$lookup": {
            "from": "activity",
            "let": {"institution_id_ref": "$_id"},
            "pipeline": [
                {"$match": {
                    "end_time": year_range,
                    "$expr": {"$or": [
                        {"$eq": ["$$institution_id_ref", "$institution_id"]},
                        {"$in": ["$$institution_id_ref", "$co_inst_ids"]}
                    ]},
                },
                },
                {"$project": {"_id": 1}},
            ],
            "as": 'oops'
        }},
        {"$unwind": "$oops"},
        {"$addFields": {"act": "$oops._id"}},
        {"$lookup": {
            "from": "activity_institution",
            "let": {"activity_ref_id": "$act"},
            "pipeline": [
                {"$match": {"is_deleted": {"$ne": True}}},
                {"$match": {"$expr": {"$eq": ["$activity_id", "$$activity_ref_id"]}}},

                {"$project": {"_id": 1}}
            ],
            "as": 'participants'
        }},
        {"$addFields": {"numOfParticipants": {"$size": "$participants"}}},
        {"$match": {"institution_name": {"$ne": None}}},
        {"$sort": {"total": -1}},
        {"$facet": {
            # 根据年获得活动数据
            "byDept": [
                {"$group": {
                    "_id": "$institution_name",
                    "total": {"$sum": "$numOfParticipants"}
                }},
                {"$sort": {"total": -1}},
                {"$limit": 5},
                {"$project": {"institution": "$_id", "_id": 0, "total": 1}},
            ]
        }}
    ]

    pipeline.extend(stage_filter)
    pipeline.extend(stage_lookup)
    result = await aggregate_docs(institution_col, pipeline)
    return result


async def crud_institution_metric_by_activity_scores(start_year, end_year=None):
    if not end_year:
        end_year = start_year

    pipeline = [
        {"$match": {"year": {"$in": [start_year, end_year]}}},
        {"$sort": {"dept_id": 1}},
        {"$group": {
            "_id": "$dept_id",
            "total_activities": {"$sum": "$total_activities"},
            "total_participants": {"$sum": "$total_participants"},
            "total_service": {"$sum": "$total_service"},
            "total_quality_index_count": {"$sum": "$quality_index_count"},
            "total_activate_indicators_count" : {"$sum": "$activate_indicators_count"},
            "total_quantity_indicators_count" : {"$sum": "$quantity_indicators_count"},
            "target": {"$first": "$target"},
            "label": {"$first": "$label"},
        }}
    ]
    result = await aggregate_docs(institution_top11_col, pipeline)
    return result
